一、今天真实发生的事
下午你反馈:13:00–14:00 左右 CPU 100%。我去查,发现一个关键事实——机器在 14:12 自动重启过(who -b 显示 system boot 2026-11-05 14:12)。你要查的 13–14 点正好在重启之前,而 journald 的日志随重启被清掉了,所以那一段是空的。
这本身就是第一个排障要点:系统重启会清空 journald 内存日志,重启前那段「谁占了 CPU」往往直接没记录。别一上来就 journalctl 翻,先确认有没有重启过。
重启前的 14:12 那条日志是 kexec -e 自动重启 + Unattended Upgrades Shutdown——不是你手动敲的,更像系统不响应后的自动恢复动作。结合「CPU 100% → 卡死 → 自动重启」的链条,高度怀疑是内存吃紧引发 swap 抖动把 CPU 打满。
二、为啥重启前的进程查不到,但重启后的线索能定位
日志没了归没了,但重启之后 gateway 自己写的运行日志(/tmp/openclaw-1000/openclaw-2026-11-05.log)还在,里面全是启动后的 WARN。这些 WARN 把根因链指向了同一个东西:OpenClaw gateway 内存持续增长。
启动后 30 分钟内,gateway 进程 RSS 从 1.52 GiB 涨到 1.99 GiB。日志里反复出现:
[gateway/diagnostics/memory] memory pressure: level=warning reason=rss_threshold
rss=1.52 GiB ... threshold=1.5 GiB thresholdRatio=101.1%
rss=1.99 GiB ... thresholdRatio=133%
还有一类高频 WARN:
[sessions/lifecycle] slow session lifecycle operation elapsedMs=1767
[sqlite/transaction] slow SQLite transaction hold elapsedMs=1025
[session-sqlite] slow SQLite session write elapsedMs=1029
[session-sqlite] slow SQLite reclamation Worker operation elapsedMs=2091
主线程被 SQLite 慢写卡住 1–2.8 秒,是内存压力下的典型连锁反应。session DB 现在 35M、WAL 4M,不算大,但有锁竞争。
三、上一次开机跑了多久,这事有多严重
who -b 之外,重启记录显示上一轮开机是 11-02 16:53 → 11-05 14:12,约 3 天。
本机总内存只有 3.4 GiB,上面还跑着 mysql、java(标了后台 8080)、docker(gitea/waline)、nginx 等。gateway 这种「随运行时长线性增长」的趋势,跑满 3 天,内存很容易见底 → 开始 swap → kswapd 占满 CPU(外显就是 CPU 100%)→ 系统卡死触发自动重启。
所以今天的 100% 不是某个脚本突然死循环,而是累积性内存增长的最终爆发。
四、gateway status --deep 为什么没指认凶手
重启后我跑了 openclaw gateway status --deep,期望拿到「每个 worker 占多少内存」的明细。结果它只输出服务/配置信息(pid 1172、连通 ok、无 Troubles 列出),没有按插件/worker 拆的内存表。
但它给了一个关键数字:
Command: node --max-old-space-size=2048 ... gateway --port 18789
也就是 gateway 给 V8 设的堆上限是 2 GiB。而实测进程 RSS 冲到了 2 GiB——已经超出这个堆帽。为什么超?日志里那句点破了:
workerMemoryScope=js-heap-only; external/ArrayBuffers are not capped;
nested workers are not included
意思是:gateway 开了 15–19 个 worker 线程(session-transcript ×2、sqlite-store、memory-search、prepared-model-catalog 等),每个 worker 各占一份 JS 堆;而 external/ArrayBuffers 不受堆上限约束,会随运行累积,且这些都不算进 2 GiB 的 V8 堆帽子。所以 RSS 能稳稳突破 2 GiB。
五、是不是 memory-core 插件在漏
不能钉死,但嫌疑最大。日志里 memory-core 高频活动:
memory_index_chunks_vec not updated — semantic vector embeddings unavailable
memory-core: normalized recall artifacts before dreaming
memory-core: dreaming promotion complete
memory-core: dream diary entry written
它的 worker 没有单独列出堆占用,无法证明「就是它漏」。更可能是多 worker 累积 + 未受上限约束的 external/ArrayBuffers 的整体增长,memory-core 只是加剧因素之一。想彻底定位,得跑 openclaw gateway diagnostics export(日志里 nextStep 推荐的那个,导出完整内存剖析)。
六、防复发该做什么
定位到「累积性内存增长」,修法就不是「查哪个进程」,而是「别让它连续跑太久」:
- 加看门狗/定时重启 gateway:每天低峰
systemctl --user restart openclaw-gateway一次,别让它跑满 3 天撑爆。这是成本最低、最稳的防复发手段。
- 跑 diagnostics export 看真凶:
openclaw gateway diagnostics export导出完整内存剖析,确认是不是 memory-core,再决定要不要调插件频率。
- 盯 session sqlite 慢写:35M 不大但已慢,可观察是否持续膨胀;必要时让 gateway 做 VACUUM / 检查点。
- 内存告警:到阈值(比如 RSS 1.8 GiB)自动重启,比等它卡死重启更优雅。
实战踩坑提醒
- 系统重启会清空 journald 内存日志:查「重启前的 100%」先
who -b确认有没有重启,否则翻到的那段是空的,别浪费时间。
gateway status --deep不拆 worker 内存:它给的是服务/配置概览,不是按插件的内存明细。要看真凶得gateway diagnostics export。
- 堆上限拦不住 RSS:
--max-old-space-size=2048只管 V8 JS 堆;external/ArrayBuffers 和 worker 堆都不算在内,RSS 照样能超。别被「2G 上限」误导。
- memory pressure 是最早信号:日志里
rss_threshold警告一出现,就该警惕了,不是等到重启才处理。
- 累积性增长别等死:这种「跑几天才爆」的问题,定时重启比事后排查划算得多。
- 当前状态是健康的:排查完测得负载 0.18、CPU 空闲、各服务正常——问题已随自动重启消除,关键是防下次。
小结
今天这台机器 13–14 点 CPU 100%、14:12 自动重启,根因是 OpenClaw gateway 内存随运行时长累积增长(多 worker + 未受 V8 堆帽约束的 external 内存),跑满约 3 天后内存见底、swap 抖动把 CPU 打满,最终触发自动重启。日志因重启被清,但重启后 gateway 自身的 memory pressure / slow SQLite 警告把链条指了出来。gateway status --deep 只给配置概览、不拆 worker 内存,真凶确认需 diagnostics export;而最实在的防复发手段是每天低峰定时重启 gateway,不让它连续跑几天。这趟从「查不到的 100%」到「看得见的内存增长」,是把运维直觉落成可复用步骤的一次实战。
扫码关注公众号,获取更多 OpenClaw 实操技巧:
